package pl.wroc.uni.ii.evolution.experimental.decisiontree;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import pl.wroc.uni.ii.evolution.experimental.EvTreeIndividual;
import pl.wroc.uni.ii.evolution.utils.EvIRandomizer;

/**
 * Decision tree individual is in fact a node. It delegates taking
 * a decision to the Decision object and its children. 
 * 
 * <p> Every node can have up to tree children. 
 * One for each value of Answer enumeration.
 * 
 * <p> When a decision strategy object returns answer for which
 * there is a child node, taking decision is delegetad to it.
 * 
 * <p> If no children are present, the node can be considered a leaf.
 * All answers are returned as they are recieved from decision strategy.
 * 
 * @author Kamil Dworakowski
 *
 * @param <T> is the type of the paramater based on which a decision is given
 */
public class EvDecisionTreeIndividual<T> extends EvTreeIndividual<EvDecisionTreeIndividual<T>> {

  private static final long serialVersionUID = 1L;

  private EvDecision<T> decision_strategy;

  Map<EvAnswer, EvDecisionTreeIndividual<T>> children = new TreeMap<EvAnswer, EvDecisionTreeIndividual<T>>();

  public EvDecisionTreeIndividual(EvDecision<T> decision_strategy) {
    super();
    this.decision_strategy = decision_strategy;
  }

  @Override
  public Object clone() {
    return this.replace(null, null);
  }

  public EvAnswer decide(T arg) {
    EvAnswer a_answer = this.decision_strategy.decide(arg);
    if (children.containsKey(a_answer))
      return children.get(a_answer).decide(arg);
    return a_answer;
  }

  public void setNodeForAnswer(EvAnswer answer, EvDecisionTreeIndividual<T> node) {
    children.put(answer, node);
  }

  public EvDecisionTreeIndividual<T> replace(
      EvDecisionTreeIndividual<T> to_replace, EvDecisionTreeIndividual<T> substitute) {

    if (this == to_replace) {  
      return substitute;
    }

    EvDecisionTreeIndividual<T> indiv = new EvDecisionTreeIndividual<T>(
        this.decision_strategy);
    for (EvAnswer answer : children.keySet()) {
      EvDecisionTreeIndividual<T> child = children.get(answer);
      indiv.children.put(answer, child.replace(to_replace, substitute));
    }
    return indiv;
  }

  
  // these are autogenerated equals and hashCode methods
  @Override
  public int hashCode() {
    final int PRIME = 31;
    int result = 1;
    result = PRIME * result + ((children == null) ? 0 : children.hashCode());
    result = PRIME * result
        + ((decision_strategy == null) ? 0 : decision_strategy.hashCode());
    return result;
  }
  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    final EvDecisionTreeIndividual other = (EvDecisionTreeIndividual) obj;
    if (children == null) {
      if (other.children != null)
        return false;
    } else if (!children.equals(other.children))
      return false;
    if (decision_strategy == null) {
      if (other.decision_strategy != null)
        return false;
    } else if (!decision_strategy.equals(other.decision_strategy))
      return false;
    return true;
  }


  public EvDecisionTreeIndividual<T> randomDescendant(EvIRandomizer randomizer) {
    List<EvDecisionTreeIndividual<T>> descendants = 
      new ArrayList<EvDecisionTreeIndividual<T>>();
    collectDescendants(descendants);
    
    return descendants.get(randomizer.nextInt(descendants.size()));
  }
 
  /**
   * Going through tree in in-order way.
   * 
   * @param descendants a collecting object
   */
  private void collectDescendants(List<EvDecisionTreeIndividual<T>> descendants) {
    descendants.add(this);
    for(EvDecisionTreeIndividual<T> dti : children.values()) {
      dti.collectDescendants(descendants);
    }
  }

  // wtf?! procedural programming? :)
  public EvDecision<T> getDecision_strategy() {
    return decision_strategy;
  }
  public Map<EvAnswer, EvDecisionTreeIndividual<T>> getChildren() {
    return children;
  }

}
